﻿using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Serilog;
using Serilog.Sinks.File;
using System.Linq.Expressions;

namespace CodeGenerate.CodeGenerate
{
    public class CodeGenerateHelper
    {
        static GeneratorConfig Config;
        static Dictionary<string, string> Templates;
        static Dictionary<string, TableSchema> TableSchemaCache = new Dictionary<string, TableSchema>();

        public static async Task Test()
        {
            // 初始化日志
            Log.Logger = new LoggerConfiguration()
                .WriteTo.Console()
                .WriteTo.File(Config?.LogFilePath ?? Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "CodeGenerator.log"), rollingInterval: RollingInterval.Day)
                .CreateLogger();

            try
            {
                Log.Information("Code generation started.");

                // 加载配置文件（从程序根目录）
                string configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "GeneratorConfig.json");
                if (!File.Exists(configPath))
                {
                    Log.Error($"Configuration file {configPath} not found.");
                    throw new FileNotFoundException($"Configuration file {configPath} not found.");
                }
                Config = JsonConvert.DeserializeObject<GeneratorConfig>(File.ReadAllText(configPath));
                Log.Information("Configuration loaded successfully.");

                // 设置默认 TemplateFolder 为根目录下的 Templates
                Config.TemplateFolder = string.IsNullOrEmpty(Config.TemplateFolder)
                    ? Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Templates")
                    : Path.Combine(AppDomain.CurrentDomain.BaseDirectory, Config.TemplateFolder);

                // 加载模板
                Templates = LoadTemplates(Config.TemplateFolder);
                Log.Information($"Loaded {Templates.Count} templates from {Config.TemplateFolder}.");

                // 缓存数据库元数据
                await CacheTableSchemasAsync(Config.ConnectionString, Config.Tables.Any() ? Config.Tables : GetAllTables(Config.ConnectionString));
                Log.Information($"Cached metadata for {TableSchemaCache.Count} tables.");

                // 生成 ApiBaseResult 相关类
                GenerateApiBaseResultClasses();
                Log.Information("Generated ApiBaseResult classes.");

                // 为每张表生成代码
                foreach (var table in TableSchemaCache.Values)
                {
                    Log.Information($"Generating code for table {table.TableName}.");
                    try
                    {
                        GenerateCode(table);
                        Log.Information($"Successfully generated code for table {table.TableName}.");
                    }
                    catch (Exception ex)
                    {
                        Log.Error($"Failed to generate code for table {table.TableName}: {ex.Message}");
                    }
                }

                Log.Information("Code generation completed successfully.");
            }
            catch (Exception ex)
            {
                Log.Error($"Code generation failed: {ex.Message}");
                throw;
            }
            finally
            {
                Log.CloseAndFlush();
            }
        }

        static List<string> GetAllTables(string connectionString)
        {
            var tables = new List<string>();
            using (var conn = new SqlConnection(connectionString))
            {
                conn.Open();
                var cmd = new SqlCommand("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'", conn);
                using (var reader = cmd.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        tables.Add(reader["TABLE_NAME"].ToString());
                    }
                }
            }
            return tables;
        }

        static async Task CacheTableSchemasAsync(string connectionString, List<string> tables)
        {
            foreach (var tableName in tables)
            {
                if (!TableSchemaCache.ContainsKey(tableName))
                {
                    var schema = new TableSchema { TableName = tableName, Columns = new List<ColumnSchema>() };
                    using (var conn = new SqlConnection(connectionString))
                    {
                        await conn.OpenAsync();
                        var cmd = new SqlCommand(
                            $@"SELECT c.COLUMN_NAME, c.DATA_TYPE, c.CHARACTER_MAXIMUM_LENGTH, 
                                      CASE WHEN pk.COLUMN_NAME IS NOT NULL THEN 1 ELSE 0 END AS IsPrimaryKey,
                                      ep.value AS Description
                               FROM INFORMATION_SCHEMA.COLUMNS c
                               LEFT JOIN sys.extended_properties ep 
                                   ON ep.major_id = OBJECT_ID('{tableName}') 
                                   AND ep.minor_id = COLUMNPROPERTY(OBJECT_ID('{tableName}'), c.COLUMN_NAME, 'ColumnId')
                               LEFT JOIN (
                                   SELECT COLUMN_NAME 
                                   FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE 
                                   WHERE TABLE_NAME = '{tableName}' 
                                   AND CONSTRAINT_NAME LIKE 'PK_%'
                               ) pk ON c.COLUMN_NAME = pk.COLUMN_NAME
                               WHERE c.TABLE_NAME = '{tableName}'", conn);
                        using (var reader = await cmd.ExecuteReaderAsync())
                        {
                            while (await reader.ReadAsync())
                            {
                                schema.Columns.Add(new ColumnSchema
                                {
                                    Name = reader["COLUMN_NAME"].ToString(),
                                    DataType = reader["DATA_TYPE"].ToString(),
                                    MaxLength = reader["CHARACTER_MAXIMUM_LENGTH"]?.ToString(),
                                    IsPrimaryKey = Convert.ToInt32(reader["IsPrimaryKey"]) == 1,
                                    Description = reader["Description"]?.ToString() ?? ""
                                });
                            }
                        }
                    }
                    TableSchemaCache[tableName] = schema;
                }
            }
        }

        static Dictionary<string, string> LoadTemplates(string templateFolder)
        {
            var templates = new Dictionary<string, string>();
            if (!Directory.Exists(templateFolder))
            {
                Log.Error($"Template folder {templateFolder} does not exist.");
                throw new DirectoryNotFoundException($"Template folder {templateFolder} does not exist.");
            }
            foreach (var file in Directory.GetFiles(templateFolder, "*.txt"))
            {
                templates[Path.GetFileNameWithoutExtension(file)] = File.ReadAllText(file);
            }
            return templates;
        }

        static void GenerateApiBaseResultClasses()
        {
            if (!Templates.ContainsKey("ApiBaseResult"))
            {
                Log.Warning("ApiBaseResult template not found, skipping generation.");
                return;
            }

            string content = Templates["ApiBaseResult"];
            var replacements = new Dictionary<string, string>
            {
                { "{Namespace}", Config.NamespacePrefix },
                { "{CreateDate}", DateTime.Now.ToString("yyyy-MM-dd") }
            };

            foreach (var replacement in replacements)
            {
                content = content.Replace(replacement.Key, replacement.Value);
            }

            string outputPath = Path.Combine(Config.OutputFolder, $"{Config.NamespacePrefix}.Domain.Shared", "ApiBaseResult.cs");
            Directory.CreateDirectory(Path.GetDirectoryName(outputPath));

            if (File.Exists(outputPath))
            {
                Log.Information($"File {outputPath} exists, skipping...");
                return;
            }

            File.WriteAllText(outputPath, content);
            Log.Information($"Generated file: {outputPath}");
        }

        static void GenerateCode(TableSchema schema)
        {
            string entityName = ToPascalCase(schema.TableName.Replace("tb_", ""));
            string properties = GenerateProperties(schema.Columns);
            string enumDefinitions = GenerateEnums(schema.Columns);
            string validationChecks = GenerateValidationChecks(schema, entityName, false);
            string validationChecksUpdate = GenerateValidationChecks(schema, entityName, true);
            string dtoAssignments = GenerateDtoAssignments(schema.Columns);

            var replacements = new Dictionary<string, string>
            {
                { "{EntityName}", entityName },
                { "{TableName}", schema.TableName },
                { "{Namespace}", Config.NamespacePrefix },
                { "{Properties}", properties },
                { "{EnumDefinitions}", enumDefinitions },
                { "{PrimaryKey}", schema.Columns.First(c => c.IsPrimaryKey).Name },
                { "{PrimaryKeyType}", MapSqlTypeToCSharp(schema.Columns.First(c => c.IsPrimaryKey).DataType) },
                { "{CreateDate}", DateTime.Now.ToString("yyyy-MM-dd") },
                { "{ValidationChecks}", validationChecks },
                { "{ValidationChecksUpdate}", validationChecksUpdate },
                { "{DtoAssignments}", dtoAssignments }
            };

            foreach (var template in Templates.Where(t => t.Key != "ApiBaseResult"))
            {
                string content = template.Value;
                foreach (var replacement in replacements)
                {
                    content = content.Replace(replacement.Key, replacement.Value);
                }

                string outputPath = GetOutputPath(template.Key, entityName);
                Directory.CreateDirectory(Path.GetDirectoryName(outputPath));

                // 增量生成
                if (File.Exists(outputPath))
                {
                    Log.Information($"File {outputPath} exists, skipping...");
                    continue;
                }

                File.WriteAllText(outputPath, content);
                Log.Information($"Generated file: {outputPath}");
            }

            // 更新 DbContext 和映射
            UpdateDbContext(entityName);
            GenerateEfMapping(entityName, schema);
        }

        static string GenerateProperties(List<ColumnSchema> columns)
        {
            var properties = new List<string>();
            foreach (var column in columns)
            {
                string csharpType = MapSqlTypeToCSharp(column.DataType);
                if (csharpType == "enum")
                {
                    csharpType = ToPascalCase(column.Name) + "Enum";
                }

                var sb = new StringBuilder();
                if (!string.IsNullOrEmpty(column.Description))
                {
                    sb.AppendLine($"        /// <summary>");
                    sb.AppendLine($"        /// {column.Description}");
                    sb.AppendLine($"        /// </summary>");
                }
                sb.AppendLine($"        [Column(\"{column.Name}\")]");
                if (column.DataType == "nvarchar" && column.MaxLength != null && column.MaxLength != "-1")
                {
                    sb.AppendLine($"        [StringLength({column.MaxLength})]");
                }
                if (!column.IsPrimaryKey && column.DataType != "datetime")
                {
                    sb.AppendLine($"        [Required]");
                }
                sb.AppendLine($"        public {csharpType} {ToPascalCase(column.Name)} {{ get; set; }}");
                properties.Add(sb.ToString());
            }
            return string.Join("\n", properties);
        }

        static string GenerateEnums(List<ColumnSchema> columns)
        {
            var enums = new List<string>();
            foreach (var column in columns.Where(c => c.DataType == "tinyint"))
            {
                string enumName = ToPascalCase(column.Name) + "Enum";
                string enumValues = ParseEnumValues(column.Description);
                enums.Add($@"public enum {enumName}
    {{
        {enumValues}
    }}");
            }
            return string.Join("\n\n", enums);
        }

        static string ParseEnumValues(string description)
        {
            if (string.IsNullOrEmpty(description) || !description.Contains("="))
            {
                return "Disabled = 0,\n        Enabled = 1";
            }

            var values = new List<string>();
            var pairs = description.Trim('(', ')').Split(',');
            foreach (var pair in pairs)
            {
                var parts = pair.Trim().Split('=');
                if (parts.Length == 2)
                {
                    values.Add($"{parts[1].Trim()} = {parts[0].Trim()}");
                }
            }
            return string.Join(",\n        ", values);
        }

        static string GenerateValidationChecks(TableSchema schema, string entityName, bool isUpdate)
        {
            var checks = new List<string>();
            if (!Config.ValidationRules.TryGetValue(schema.TableName, out var rules))
                return "";

            // 单一字段唯一性验证
            if (rules.UniqueFields != null)
            {
                foreach (var field in rules.UniqueFields)
                {
                    string propertyName = ToPascalCase(field);
                    string condition = isUpdate
                        ? $"await _repository.ExistsByUniqueFieldsAsync(\"{propertyName}\", input.{propertyName}, id)"
                        : $"await _repository.ExistsByUniqueFieldsAsync(\"{propertyName}\", input.{propertyName})";
                    checks.Add($@"
            if ({condition})
            {{
                return new ApiResult<CommonOperationResult>
                {{
                    IsSuccess = false,
                    Message = L(""Error:{propertyName}AlreadyExists"", input.{propertyName})
                }};
            }}");
                }
            }

            // 联合唯一性验证
            if (rules.JointUniqueFields != null)
            {
                foreach (var fieldGroup in rules.JointUniqueFields)
                {
                    string propertyNames = string.Join(", ", fieldGroup.Select(ToPascalCase));
                    string fieldValues = string.Join(", ", fieldGroup.Select(f => $"input.{ToPascalCase(f)}"));
                    string fieldNamesArray = string.Join(", ", fieldGroup.Select(f => $"\"{ToPascalCase(f)}\""));
                    string condition = isUpdate
                        ? $"await _repository.ExistsByJointUniqueFieldsAsync(new[] {{ {fieldNamesArray} }}, new object[] {{ {fieldValues} }}, id)"
                        : $"await _repository.ExistsByJointUniqueFieldsAsync(new[] {{ {fieldNamesArray} }}, new object[] {{ {fieldValues} }})";
                    checks.Add($@"
            if ({condition})
            {{
                return new ApiResult<CommonOperationResult>
                {{
                    IsSuccess = false,
                    Message = L(""Error:JointUniqueFieldsAlreadyExists"", ""{propertyNames}"")
                }};
            }}");
                }
            }

            // 范围验证
            if (rules.RangeFields != null)
            {
                foreach (var field in rules.RangeFields)
                {
                    string propertyName = ToPascalCase(field.Key);
                    checks.Add($@"
            if (input.{propertyName} < {field.Value.Min} || input.{propertyName} > {field.Value.Max})
            {{
                return new ApiResult<CommonOperationResult>
                {{
                    IsSuccess = false,
                    Message = L(""Error:{propertyName}OutOfRange"", input.{propertyName}, {field.Value.Min}, {field.Value.Max})
                }};
            }}");
                }
            }

            // 正则表达式验证
            if (rules.RegexFields != null)
            {
                foreach (var field in rules.RegexFields)
                {
                    string propertyName = ToPascalCase(field.Key);
                    checks.Add($@"
            if (!Regex.IsMatch(input.{propertyName}, @""{field.Value}""))
            {{
                return new ApiResult<CommonOperationResult>
                {{
                    IsSuccess = false,
                    Message = L(""Error:{propertyName}InvalidFormat"", input.{propertyName})
                }};
            }}");
                }
            }

            return string.Join("\n", checks);
        }

        static string GenerateDtoAssignments(List<ColumnSchema> columns)
        {
            var assignments = new List<string>();
            foreach (var column in columns)
            {
                if (column.IsPrimaryKey || column.Name.EndsWith("_date")) // 跳过主键和审计字段
                    continue;
                string propertyName = ToPascalCase(column.Name);
                assignments.Add($"            entity.{propertyName} = input.{propertyName};");
            }
            return string.Join("\n", assignments);
        }

        static string MapSqlTypeToCSharp(string sqlType)
        {
            return Config.TypeMappings.TryGetValue(sqlType.ToLower(), out var csharpType) ? csharpType : "string";
        }

        static string ToPascalCase(string input)
        {
            return string.Join("", input.Split('_').Select(s => char.ToUpper(s[0]) + s.Substring(1).ToLower()));
        }

        static string GetOutputPath(string templateKey, string entityName)
        {
            return templateKey switch
            {
                "Entity" => Path.Combine(Config.OutputFolder, $"{Config.NamespacePrefix}.Core", $"{entityName}.cs"),
                "Enum" => Path.Combine(Config.OutputFolder, $"{Config.NamespacePrefix}.Domain.Shared", $"{entityName}Enums.cs"),
                "EfMapping" => Path.Combine(Config.OutputFolder, $"{Config.NamespacePrefix}.EntityFrameworkCore", "Mappings", $"{entityName}Map.cs"),
                "CreateDto" => Path.Combine(Config.OutputFolder, $"{Config.NamespacePrefix}.Application", "Dtos", $"{entityName}CreateDto.cs"),
                "UpdateDto" => Path.Combine(Config.OutputFolder, $"{Config.NamespacePrefix}.Application", "Dtos", $"{entityName}UpdateDto.cs"),
                "Dto" => Path.Combine(Config.OutputFolder, $"{Config.NamespacePrefix}.Application", "Dtos", $"{entityName}Dto.cs"),
                "PagedRequestDto" => Path.Combine(Config.OutputFolder, $"{Config.NamespacePrefix}.Application", "Dtos", $"{entityName}PagedRequestDto.cs"),
                "AppServiceInterface" => Path.Combine(Config.OutputFolder, $"{Config.NamespacePrefix}.Application.Contracts", $"I{entityName}AppService.cs"),
                "AppService" => Path.Combine(Config.OutputFolder, $"{Config.NamespacePrefix}.Application", $"{entityName}AppService.cs"),
                "RepositoryInterface" => Path.Combine(Config.OutputFolder, $"{Config.NamespacePrefix}.Core", $"I{entityName}Repository.cs"),
                "RepositoryImplementation" => Path.Combine(Config.OutputFolder, $"{Config.NamespacePrefix}.EntityFrameworkCore", $"{entityName}Repository.cs"),
                "Controller" => Path.Combine(Config.OutputFolder, $"{Config.NamespacePrefix}.HttpApi", "Controllers", $"{entityName}Controller.cs"),
                "UnitTest" => Path.Combine(Config.OutputFolder, $"{Config.NamespacePrefix}.Tests", $"{entityName}AppServiceTests.cs"),
                _ => throw new ArgumentException($"Unknown template: {templateKey}")
            };
        }

        static void UpdateDbContext(string entityName)
        {
            string dbContextPath = Path.Combine(Config.OutputFolder, $"{Config.NamespacePrefix}.EntityFrameworkCore", "MyAppDbContext.cs");
            if (!File.Exists(dbContextPath))
            {
                File.WriteAllText(dbContextPath, $@"using Microsoft.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;
using {Config.NamespacePrefix}.Core;

namespace {Config.NamespacePrefix}.EntityFrameworkCore
{{
    public class MyAppDbContext : AbpDbContext<MyAppDbContext>
    {{
        public MyAppDbContext(DbContextOptions<MyAppDbContext> options) : base(options) {{ }}
        public DbSet<{entityName}> {entityName}s {{ get; set; }}

        protected override void OnModelCreating(ModelBuilder builder)
        {{
            base.OnModelCreating(builder);
            builder.Configure();
        }}
    }}
}}");
                Log.Information($"Created DbContext: {dbContextPath}");
            }
            else
            {
                string content = File.ReadAllText(dbContextPath);
                if (!content.Contains($"public DbSet<{entityName}>"))
                {
                    int insertIndex = content.IndexOf("public class MyAppDbContext") + content.Substring(content.IndexOf("public class MyAppDbContext")).IndexOf("{") + 1;
                    content = content.Insert(insertIndex, $"\n        public DbSet<{entityName}> {entityName}s {{ get; set; }}\n");
                    File.WriteAllText(dbContextPath, content);
                    Log.Information($"Updated DbContext with {entityName} entity.");
                }
            }
        }

        static void GenerateEfMapping(string entityName, TableSchema schema)
        {
            string mappingPath = Path.Combine(Config.OutputFolder, $"{Config.NamespacePrefix}.EntityFrameworkCore", "Mappings", $"{entityName}Map.cs");
            string content = $@"using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using {Config.NamespacePrefix}.Core;

namespace {Config.NamespacePrefix}.EntityFrameworkCore.Mappings
{{
    public class {entityName}Map : IEntityTypeConfiguration<{entityName}>
    {{
        public void Configure(EntityTypeBuilder<{entityName}> builder)
        {{
            builder.ToTable(""{schema.TableName}"");
            builder.HasKey(x => x.Id);
{GenerateEfProperties(schema.Columns)}
            builder.Property(x => x.IsDeleted).HasDefaultValue(false);
        }}
    }}
}}";

            Directory.CreateDirectory(Path.GetDirectoryName(mappingPath));
            File.WriteAllText(mappingPath, content);
            Log.Information($"Generated EF mapping: {mappingPath}");
        }

        static string GenerateEfProperties(List<ColumnSchema> columns)
        {
            var properties = new List<string>();
            foreach (var column in columns)
            {
                string propertyName = ToPascalCase(column.Name);
                var sb = new StringBuilder();
                sb.AppendLine($"            builder.Property(x => x.{propertyName}).HasColumnName(\"{column.Name}\");");
                if (column.DataType == "nvarchar" && column.MaxLength != null && column.MaxLength != "-1")
                {
                    sb.AppendLine($"            builder.Property(x => x.{propertyName}).HasMaxLength({column.MaxLength});");
                }
                if (!column.IsPrimaryKey && column.DataType != "datetime")
                {
                    sb.AppendLine($"            builder.Property(x => x.{propertyName}).IsRequired();");
                }
                properties.Add(sb.ToString());
            }
            return string.Join("\n", properties);
        }
    }

    class GeneratorConfig
    {
        public string ConnectionString { get; set; }
        public string TemplateFolder { get; set; }
        public string OutputFolder { get; set; }
        public string LogFilePath { get; set; }
        public string NamespacePrefix { get; set; }
        public List<string> Tables { get; set; }
        public Dictionary<string, string> TypeMappings { get; set; }
        public Dictionary<string, ValidationRule> ValidationRules { get; set; }
    }

    class ValidationRule
    {
        public List<string> UniqueFields { get; set; }
        public List<List<string>> JointUniqueFields { get; set; }
        public Dictionary<string, RangeField> RangeFields { get; set; }
        public Dictionary<string, string> RegexFields { get; set; }
    }

    class RangeField
    {
        public double Min { get; set; }
        public double Max { get; set; }
    }

    class TableSchema
    {
        public string TableName { get; set; }
        public List<ColumnSchema> Columns { get; set; }
    }

    class ColumnSchema
    {
        public string Name { get; set; }
        public string DataType { get; set; }
        public string MaxLength { get; set; }
        public bool IsPrimaryKey { get; set; }
        public string Description { get; set; }
    }
}
